local motion = {}

local function prepare(object, name)
    if not object._motion then
        object._motion = {}
    end
    
    if object._motion[name] then
        transition.cancel(object._motion[name])
    end
end

local function cancel(object, name)
    if object._motion and object._motion[name] then
        transition.cancel(object._motion[name])
    end  
end

function motion.clean(object)

    if type(object) ~= 'table' then return end
    
    if object.numChildren then
        for i=1, object.numChildren, 1 do
            motion.clean(object[i])
        end
    end
    
    if object._motion then
        for key, value in pairs(object._motion) do
            transition.cancel(value)
        end
        object._motion = nil;
    end
end

function motion.Shake(object, time, rotationMin, rotationMax, iterations)
    
    local name = "shake";

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    local function shake()
        iterations = iterations - 1;
        if iterations > 0 then
            local rotation = object.rotation > rotationMin and rotationMin or rotationMax; 
            object._motion[name] = transition.to(object, {time=time, rotation=rotation, onComplete=shake})
        else
            object:dispatchEvent{name="Motion"; motion=name, action="complete"}
        end
    end
    shake();
end

function motion.SlideIn(object, time, delay, yOffset)
    
    local name = "slideIn";

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.c)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
    end
    
    local yOffset = yOffset or 0;
    object.x = centerX
    object.y = centerY + yOffset
    object._motion[name] = transition.from(object, {time=time, delay=delay or 0, transition=easing.outExpo, y=screenTop - object.height, onComplete=onComplete})
end

function motion.SlideOut(object, time, delay, autoRemove)
    
    local name = "slideOut";

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.tl)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
        if autoRemove then
            object:removeSelf()
            object = nil
        end
    end

    object.isVisible, object.alpha = true, 1.0
    object._motion[name] = transition.to(object, {time=time, delay=delay or 0, transition=easing.outExpo, y=screenTop - object.height, onComplete=onComplete})
end

function motion.FadeIn(object, time, delay)
    
    local name = "fadeIn";

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.tl)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
    end

    object.isVisible, object.alpha = true, 0
    object._motion['fadeIn'] = transition.to(object, {time=time, delay=delay or 0, transition=easing.outExpo, alpha=1, onComplete=onComplete})
end

function motion.FadeOut(object, time, delay, autoRemove)
    
    local name = "fadeOut";
   
    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.tl)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
        if autoRemove then
            object:removeSelf()
            object = nil
        end
    end
    
    object.isVisible, object.alpha = true, 1.0
    object._motion[name] = transition.to(object, {time=time, delay=delay or 0, transition=easing.outExpo, alpha=0, onComplete=onComplete})
end

function motion.ExplodeIn(object, time, delay)
    
    local name = "explodeIn";

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.c)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
    end
    
    object.alpha = 0
    object.xScale = 2
    object.yScale = 2
    object.isVisible, object.alpha = true, 1.0    
    object._motion[name] = transition.to(object, {time=time, delay=delay or 0, transition=easing.outExpo, alpha=1, xScale=1, yScale=1, onComplete=onComplete})
end

function motion.ExplodeOut(object, time, delay, autoRemove)
    
    local name = "explodeOut";
    
    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    object:setReferencePoint(display.tl)
    
    local function onComplete()
        cancel(object, name)
        object:dispatchEvent{name="Motion"; motion=name, action="complete"}
        if autoRemove then
            object:removeSelf()
            object = nil
        end
    end

    object.isVisible, object.alpha = true, 1.0
    object._motion[name] = transition.to(object, {time=time, delay=delay or 0, transition=easing.outExpo, alpha=0, xScale=2, yScale=2, onComplete=onComplete})
end

function motion.Pulse(object, time, alphaMin, alphaMax)
    
    local name = "pulse"

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    local function pulse()
        local alpha = object.alpha > alphaMin and alphaMin or alphaMax; 
        object._motion[name] = transition.to(object, {time=time, alpha=alpha, onComplete=pulse})
    end
    pulse();
end

function motion.Wobble(object, time, xScaleMax, yScaleMax)
    
    local name = "wobble"

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name);
    
    local function wobble()
        if not state or state == "horizontal" then
            state = "vertical"
        elseif state == "vertical" then
            state = "horizontal"
        end
        local xScale = state == "horizontal" and xScaleMax or 1
        local yScale = state == "vertical" and yScaleMax or 1
        
        object._motion[name] = transition.to(object, {time=time, xScale=xScale, yScale=yScale, transition=easing.inOutQuad, onComplete=wobble})
    end
    wobble();
end

function motion.Scale(object, time, scale, reverse)
    
    local name = "scale"

    if type(time) == "boolean" and time == false then
        cancel(object, name)
        return
    end

    prepare(object, name)
    
    if reverse then
        time = time / 2
    end
    
    local function scaleComplete()
        transition.cancel(object._motion[name]);
        if reverse then
            reverse = false
            scale = 1
            object._motion[name] = transition.to(object, {time=time, xScale=scale, yScale=scale, onComplete=scaleComplete})
        else
            cancel(object, name)
            object:dispatchEvent{name="Motion"; motion=name, action="complete"}
        end
    end
    
    object._motion[name] = transition.to(object, {time=time, xScale=scale, yScale=scale, onComplete=scaleComplete})
end

return motion